package userlogic

import (
	"context"
	"math/rand"
	"strconv"
	"time"

	"gitee.com/liuxuezhan/ar-platform/base-common/uuid"
	ar "gitee.com/liuxuezhan/ar-platform/jwttoken"
	"gitee.com/liuxuezhan/ar-platform/login-rpc/internal/dao/model"
	"gitee.com/liuxuezhan/ar-platform/login-rpc/internal/dao/schema"
	"gitee.com/liuxuezhan/ar-platform/login-rpc/internal/svc"
	"gitee.com/liuxuezhan/ar-platform/login-rpc/login_rpc"
	config "gitee.com/liuxuezhan/ar-platform/login-rpc/pkg/code"
	re "gitee.com/liuxuezhan/ar-platform/login-rpc/pkg/redis_utils"
	"gitee.com/liuxuezhan/ar-platform/login-rpc/pkg/tool"

	"github.com/zeromicro/go-zero/core/logc"
	"github.com/zeromicro/go-zero/core/logx"
)

type LoginLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
	return &LoginLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *LoginLogic) Login(in *login_rpc.LoginRequest) (*login_rpc.LoginResponse, error) {
	commonResp := login_rpc.CommonResp{}
	if in.Account == "" || in.Password == "" {
		commonResp.Code = config.ParameterError
		commonResp.Message = "Please enter your password"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	if in.LoginType == 0 {
		commonResp.Code = config.ParameterError
		commonResp.Message = "login type is 0"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	if in.Source == "" || in.Ip == "" {
		commonResp.Code = config.ParameterError
		if in.Source == "" {
			commonResp.Message = "empty platform"
		}
		if in.Ip == "" {
			commonResp.Message = "empty request IP"
		}
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	var err error
	var userId int64
	if in.LoginType == 1 {
		userId, _ = model.NewUserTotalModel(l.ctx, l.svcCtx.Db).GetUserByPhone(in.Account, in.PhoneCode)
	} else if in.LoginType == 2 {
		userId, _ = model.NewUserTotalModel(l.ctx, l.svcCtx.Db).GetUserByEmail(in.Account)
	} else {
		commonResp.Code = config.ParameterError
		commonResp.Message = "unsupported login type"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}
	if userId == 0 {
		commonResp.Code = config.ParameterError
		commonResp.Message = "no such user"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	// 查询密码
	user, _ := model.NewUserModel(l.ctx, l.svcCtx.Db).GetUserByUserId(userId)

	if user.Status != 0 {
		commonResp.Code = config.BasicError
		commonResp.Message = "user has been disabled"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	if HashPassword(in.Password) != user.Password {
		commonResp.Code = config.ParameterError
		commonResp.Message = "The password is wrong"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	// 谷歌验证
	if user.IsGoogle == 1 {
		if in.GoogleCode == "" {
			commonResp.Code = config.ParameterError
			commonResp.Message = "google code is required"
			return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
		}
		i, _ := strconv.ParseUint(in.GoogleCode, 10, 32)
		err := tool.CheckGoogleAuthCode(uint32(i), user.GoogleCode)
		if err != nil {
			commonResp.Code = config.ParameterError
			commonResp.Message = "wrong google auth code"
			return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
		}
	}

	const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	sign := make([]byte, 10)
	for i := range sign {
		sign[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	// 用户id与平台组合生成token
	encrypt, _, err := ar.Generate2Token(userId, in.Source, in.Business, string(sign), false, user.Nickname)
	if err != nil {
		commonResp.Code = config.TokenError
		commonResp.Message = "failed to generate token"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	db := l.svcCtx.Db.Begin()
	// 查询该平台token是否存在，存在则更新，不存在则插入
	id, _ := model.NewUserLoginTokenModel(l.ctx, db).GetUserLoginTokenByUserIdAndSource(userId, in.Source, in.Business)
	if id > 0 {
		err = model.NewUserLoginTokenModel(l.ctx, db).UpdateUserLoginToken(id, string(sign), userId)
		if err != nil {
			db.Rollback()
			commonResp.Code = config.DBError
			commonResp.Message = "update user login token failed"
			return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
		}
	} else {
		var userLoginToken schema.UserLoginToken
		userLoginToken.UserId = userId
		userLoginToken.Source = in.Source
		userLoginToken.Sign = string(sign)
		userLoginToken.Business = in.Business
		err = model.NewUserLoginTokenModel(l.ctx, db).InsertUserLoginToken(&userLoginToken)
		if err != nil {
			db.Rollback()
			commonResp.Code = config.DBError
			commonResp.Message = "add user login token failed"
			return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
		}
	}
	// 添加登录日志
	userLoginToken := buildLoginLog(userId, in)
	err = model.NewUserLoginLogsModel(l.ctx, db).InsertUserLoginLogs(&userLoginToken)
	if err != nil {
		db.Rollback()
		commonResp.Code = config.DBError
		commonResp.Message = "add login log failed"
		return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
	}

	// 修改首次登录时间
	if user.FirstLoginTime == 0 {
		user.FirstLoginTime = time.Now().Unix()
		err = model.NewUserModel(l.ctx, db).UpdateUserFirstLoginTime(userId, time.Now().Unix())
		if err != nil {
			db.Rollback()
			commonResp.Code = config.DBError
			commonResp.Message = "Update User FirstLoginTime failed"
			return &login_rpc.LoginResponse{CommonResp: &commonResp}, nil
		}
	}

	db.Commit()

	err = re.SetTokenRedis(l.ctx, l.svcCtx.Config.RedisConfig, user.Id, false, in.Business, in.Source, string(sign))
	logc.Error(l.ctx, "------SetTokenRedis,err:", err)

	return &login_rpc.LoginResponse{UserId: userId, CommonResp: &commonResp, Token: encrypt, LastLoginTime: user.LastOnlineTime}, nil
}

func HashPassword(password string) string {
	return uuid.HashPassword(password)
}

func buildLoginLog(userId int64, in *login_rpc.LoginRequest) (userLoginToken schema.UserLoginLogs) {
	userLoginToken.Ip = in.Ip
	userLoginToken.Device = in.Source
	userLoginToken.UserId = userId
	userLoginToken.LoginAt = time.Now().Unix()
	userLoginToken.Source = in.Source
	userLoginToken.CountryString = in.CountryString
	userLoginToken.CountryZh = in.CountryZh
	userLoginToken.City = in.City
	userLoginToken.CityZh = in.CityZh
	userLoginToken.DeviceName = in.DeviceName
	userLoginToken.ChannelId = in.ChannelId
	userLoginToken.ChannelName = in.ChannelName
	userLoginToken.AdId = in.AdId
	userLoginToken.AdName = in.AdName
	userLoginToken.DomainName = in.DomainName
	userLoginToken.UserAgent = in.UserAgent
	return
}
